package ua.yandex.shad.liubotatemperature;

/**
 *
 * @author Яна Любота
 */
import java.util.ArrayList;
import Jama.Matrix;
import Jama.SingularValueDecomposition;

public class TemperatureSeriesAnalysis {

    private final double eps = 1E-10;

    private final double[] temperatureSeries;
    
    private boolean checkLength(int size){
        if(this.temperatureSeries == null){
            //unreachable branch at normal conditions
            throw new NullPointerException();
        }
        return this.temperatureSeries.length > size;
    }

    public TemperatureSeriesAnalysis(double[] temperatureSeries) {
        this.temperatureSeries = temperatureSeries;
    }

    public double calcAverageTemp() {  //подсчет средней температуры
        if (!checkLength(0)) {
            return 0;
        }
        
        double sum = 0;
        for (double d : temperatureSeries) {
            sum += d;
        }
        return sum / temperatureSeries.length;
    }

    public double calcTempDeviation() { //подсчет температурного отклонения
        if (!checkLength(0)) {
            return 0;
        }
        double deviation = 0;
        for (int i = 0; i < this.temperatureSeries.length; i++) {
            deviation += this.temperatureSeries[i] * this.temperatureSeries[i];
        }

        deviation -= Math.pow(calcAverageTemp(), 2);
        return deviation;
    }

    public double findTempClosestToZero() { //поиск ближайшей к нулю температуры
        if (!checkLength(0)) {
            return 0;
        }
        
        double a = temperatureSeries[0]; // а= первому эллементу масива температур
        for (double d : temperatureSeries) {
            if (Math.abs(d) < Math.abs(a)) {
                a = d;
            }
        }

        return a;
    }

    public double findTempClosestToValue(double tempValue) { //поиск температуры ближайшей к значению
        double a = temperatureSeries[0];
        double c = Math.abs(a - tempValue);
        double b;
        for (double d : temperatureSeries) {
            b = Math.abs(d - tempValue);
            if (b < c) {
                c = b;
            }
        }

        return c;
    }

    public double[] findTempsLessThen(double tempValue) { //поиск температуры ниже чем
        ArrayList<Double> a;
        a = new ArrayList<>();
        for (int i = 0; i < temperatureSeries.length; i++) {
            if (temperatureSeries[i] < tempValue) {
                a.add(temperatureSeries[i]);
            }
        }
       
        double[] unboxer;
        unboxer = new double[a.size()];
        for (int i = 0; i < a.size(); i++) {
            unboxer[i] = a.get(i);
        }
        return unboxer;
    }

    public double[] findTempsGreaterThen(double tempValue) { //поиск температуры больше чем
        ArrayList<Double> a;
        a = new ArrayList<>();
        for (int i = 0; i < temperatureSeries.length; i++) {
            if (temperatureSeries[i] > tempValue) {
                a.add(temperatureSeries[i]);
            }
        }
        // return a;
        double[] unboxer;
        unboxer = new double[a.size()];
        for (int i = 0; i < a.size(); i++) {
            unboxer[i] = a.get(i);
        }
        return unboxer;
    }

    private double forecastNext(double[][] pred, Matrix V, Matrix w) {
        Matrix Q = new Matrix(pred);
        Matrix res = w.times((V.transpose().times(V)).inverse())
                .times(V.transpose()).times(Q);
        return res.get(0, 0);
    }

    public double[] forecastTemp() throws LowLenghtException {
        
        
        if (this.temperatureSeries.length < 10) {
            throw new LowLenghtException();
        }
        int winLen;
        int winTime;

        winLen = this.temperatureSeries.length / 2;
        winTime = this.temperatureSeries.length + 1 - winLen;
        double[][] trajectArray = new double[winLen][winTime];
        for (int i = 0; i < winLen; i++) {
            for (int j = 0; j < winTime; j++) {
                trajectArray[i][j] = this.temperatureSeries[i + j];
            }
        }

        Matrix trajectMatrix = new Matrix(trajectArray);

        Matrix covarMatrix = (trajectMatrix.times(trajectMatrix.transpose()))
                .times(1. / winTime);

        SingularValueDecomposition decomposed = covarMatrix.svd();

        double[] singularVals = decomposed.getSingularValues();
        int size = 0;
        for (int i = 0; i < singularVals.length; i++) {
            if (Math.abs(singularVals[i]) > eps) {
                size++;
            }
        }
        int[] mainGroup = new int[size];
        size = 0;
        for (int i = 0; i < singularVals.length; i++) {
            if (Math.abs(singularVals[i]) > eps) {
                mainGroup[size] = i;
                size++;
            }
        }

        Matrix directMatrix = decomposed.getU().getMatrix(0, winLen - 1, mainGroup);
        Matrix upperD = directMatrix.getMatrix(0, winLen - 2, 0, size - 1);
        Matrix lowerD = directMatrix.getMatrix(winLen - 1, winLen - 1, 0, size - 1);

        double[] result = new double[3];
        double[][] pred = new double[winLen - 1][1];
        for (int i = winTime; i < this.temperatureSeries.length; i++) {
            pred[i - winTime][0] = this.temperatureSeries[i];
        }
        result[0] = forecastNext(pred, upperD, lowerD);
        for (int i = 1; i < pred.length; i++) {
            pred[i - 1][0] = pred[i][0];
        }
        pred[winLen - 2][0] = result[0];
        result[1] = forecastNext(pred, upperD, lowerD);
        for (int i = 1; i < pred.length; i++) {
            pred[i - 1][0] = pred[i][0];
        }
        pred[winLen - 2][0] = result[1];
        result[2] = forecastNext(pred, upperD, lowerD);

        return result;
    }
}
